--[[

	Gruppenmitglieder:
		Liang Shi			(0352776)
		Bogdan Ciobotaru	(337005)
		Eik Tauber			(301761)

  ]]



-- should be integrated in classes.lua DONE
--TODO all over: change k1,v1 with notations that make sense! :P

--[[
	the following is now done in classes.lua (in _GLOBAL_MT), since the functions for __index had to be merged

_GLOBAL_MT = {__index = function(t,k)
						if k == 'Aspect' then
							--setmetatable(_G, {__index=function(t,k) if k=='Aspect' then error("'Aspect' is a keyword") end
							--								if k=='adapts'}
						return _createAspect
						end}
--]]


_aspects = {}

function _in_table ( t, value )
	for _, v in pairs(t) do
		if v == value then
			return true
		end
	end
	return false
end

--[[
	returns table of all Aspects, which are adapting the given class
]]
function _getAdaptingAspectsForClass(class, subClassesPreferred)

	local result			= {};
	local orderedClasses	= {};
	local classPointer		= class;
	repeat
		if subClassesPreferred then
			table.insert(orderedClasses, classPointer);
		else
			table.insert(orderedClasses, 1, classPointer);
		end
		classPointer = classPointer._super;
	until classPointer == nil

	for _,currentClass in ipairs(orderedClasses) do
		for _,v in ipairs(currentClass._adaptedBy) do
			if not _in_table(result, v) then
				table.insert(result, v);
			end
		end
	end
	return result;
end


function _exists_attribute(attribute, aspectDeclaredType, baseClass)
	--TODO check in other aspects which adapt this class DONE
--[[
	for k,v in pairs (baseClass._attributes) do
		if k == attribute then
			if aspectDeclaredType ~= v then return true, v
			end
		end
	end
]]
	if baseClass._attributes[attribute] ~= nil
	then
		if aspectDeclaredType ~= baseClass._attributes[attribute]
		then
			local name_of_type;
			if type(baseClass._attributes[attribute]) == 'table'
			then
				name_of_type = baseClass._attributes[attribute]._name;
			else
				name_of_type = baseClass._attributes[attribute];
			end
			return true, name_of_type
		else
			return false, aspectDeclaredType
		end
	end

	for k,v in pairs (_aspects) do
		local currentAspect = v
		for k1,v1 in pairs(currentAspect._adapted_classes) do
			if v1._name == baseClass._name then
				for k2, v2 in pairs(currentAspect._attributes) do
					if tostring(k) == attribute then
						if aspectDeclaredType ~= v then return true, v
						end
					end
				end
			end
		end
	end
	return false, aspectDeclaredType
end


function _aspectsDeclareAttribute(class, attribute_name)
	for index, aspect in pairs(class._adaptedBy) do
		if		aspect._attributes[attribute_name] ~= nil
			and	aspect._active
		then
			return true;
		end
	end
	if class._super ~= nil then	-- if attribute could not be found in the aspects of class, recursively test the same with the superClasses
		return _aspectsDeclareAttribute(class._super, attribute_name)
	end
	return false;					-- nothing was found
end

--[[
	searches for a declaration of attribute_name in the Aspects of class and returns its declared type
]]
function _findDeclaration(class, attribute_name)
	for index, aspect in pairs(class._adaptedBy) do
		if aspect._attributes[attribute_name] ~= nil
		then
			return aspect._attributes[attribute_name];
		end
	end
	if class._super ~= nil then	-- if attribute could not be found in the aspects of class, recursively test the same with the superClasses
		return _findDeclaration(class._super, attribute_name)
	end
	return false;					-- nothing was found (this should not happen)
end

function _createAspect (params)
	if	params[1] == nil
		or	type(params[1]) ~= String
	then
		error("Error: expected aspect name as first parameter");
	end

	local newAspect = {}

	--check if Aspect has already been declared, otherwise creates it with an aspect identifier
	--TODO check if there's a class with same name DONE
	if	_aspects[params[1]] ~= nil then
		if type(_aspects[params[1]]) == 'table'
			and _aspects[params[1]][_is_aspect] or _isClass(_G[params[1]]) then
				error('Error: cannot redeclare "' .. params[1] .. '"')
		end
	else
		_aspects[params[1]]	= newAspect
		_G[params[1]]		= newAspect
	end

	newAspect._is_aspect		= true
	newAspect._name				= params[1]
	newAspect._adapted_classes	= {}
	newAspect._attributes		= {}
	newAspect._beforeMethods	= {}
	newAspect._afterMethods		= {}
	newAspect._methods			= {}		-- while _beforeMethods and _afterMethods only hold the declaration of the methods, this table holds the real functions
	newAspect._active			= false

	for k,v in pairs(params) do	-- iterate through all entries of param
		if k ~= 1 then		-- except the first one, since it is the name of the aspect and was handled above
			if type(v) == 'table' then
				if 	k == 'adapts' then
					for index, class in pairs(v) do
						if 	not _isClass(class)
						then
							if		type(class) == 'table'
								and	class._name_of_var ~= nil
							then
								error('Error: "' .. class._name_of_var .. '" is not a class')
							else
								error("Error: entry of table adapted at position ["..index.."] is not a class")
							end
						else
							table.insert(class._adaptedBy, newAspect);
							table.insert(newAspect._adapted_classes, class)
						end
					end
				elseif k == 'attributes' then
					local wasDeclared
					local declaredType

					for attr_name, attr_type in pairs(v) do
						if	type(attr_name) == 'string' then	-- here we want to make sure the attribute has an associative index
																--  instead of a numeric one, e.g. {age = number} and not {number}
																--  table entries with an numeric index will be ignored

							if		attr_type == String			-- type checking of the attribute
								or	attr_type == Number			-- allowed types: String, Number, Boolean, any Class
								or	attr_type == Boolean
								or	_isClass(attr_type)
							then
								for index, base_class in pairs(newAspect._adapted_classes) do
									wasDeclared, declaredType = _exists_attribute(attr_name, attr_type, base_class)
									if 	wasDeclared
									then
										error("Error: Attribute with same name but with type "..declaredType.." already declared!")
									end
								end
							else							-- error if type checking failed
								if		type(attr_type)			== 'table'
									and attr_type._name_of_var	~= nil
								then
									error('Error: unknown type "' .. attr_type._name_of_var .. '"!');
								else
									error('Error: unknown type for "' .. attr_name .. '"!');
								end
							end
							newAspect._attributes[attr_name] = attr_type
						else
							error("Error in attributes: key = value expected")
						end
					end

				elseif		k == 'before'
						or	k == 'after'
				then
					for method, pattern in pairs(v) do
						if type(pattern) ~= 'string' then
							error("Error: false declaration of method pattern: String expected");
						end
						newAspect['_' .. k .. 'Methods'][method] = pattern	-- newAspect._afterMethods or newAspect._beforeMethods
					end
				else
					error("Error: False aspect declaration. Expected form: 'adapts = {list of classes}, attributes = {list of attributes}, before = {list fo befor-methods}, after = {list of after-methods}'")
				end
			else
				error("Error: False aspect declaration. Expected form: 'adapts = {list of classes}, attributes = {list of attributes}, before = {list fo befor-methods}, after = {list of after-methods}'")

			end
		end
	end

	function newAspect:_activate()
		self._active = true
	end
	function newAspect:_deactivate()
		self._active = false
	end

	local mt = {};

	mt.__index = function(t,k)
		if k == 'activate' then
			return t._activate
		end
		if k =='deactivate' then
			return t._deactivate
		end
	end

	mt.__newindex = function(t,k,v)
		if type(v) ~= 'function' then
			error('Error: cannot declare attributes on existing aspects!');
		end

		if t._methods[k] ~= nil then
			error('Error: method "' .. k ..'" is already defined!');
		end

--[[
	do we want to disallow adding mehtods to the Aspect which have not been declared (in after={...} or before={...})?
	let's say yes (otherwise just comment the following if-then-statement)
]]
		if		t._beforeMethods[k]	== nil
			and	t._afterMethods[k]	== nil
		then
			error('Error: cannot find declaration of method "' .. k ..'" in aspect!');
		end

		t._methods[k] = v;
	end

	setmetatable(newAspect, mt);

	setmetatable(_G, _GLOBAL_MT);




end
